﻿using EasyAbp.Abp.PhoneNumberLogin.Identity;
using EasyAbp.Abp.PhoneNumberLogin.Localization;
using IdentityServer4.Models;
using IdentityServer4.Validation;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.Security.Claims;

namespace EasyAbp.Abp.PhoneNumberLogin
{
    public class PhonePasswordGrantValidator : IExtensionGrantValidator, ITransientDependency
    {
        private readonly IStringLocalizer<PhoneNumberLoginResource> _localizer;
        private readonly IOptions<IdentityOptions> _identityOptions;
        private readonly IUniquePhoneNumberIdentityUserRepository _uniquePhoneNumberIdentityUserRepository;
        private readonly IdentityUserManager _identityUserManager;
        private ILogger<PhonePasswordGrantValidator> _logger;
        public PhonePasswordGrantValidator(
          IStringLocalizer<PhoneNumberLoginResource> localizer,
          IdentityUserManager identityUserManager,
          IOptions<IdentityOptions> identityOptions,
          IUniquePhoneNumberIdentityUserRepository uniquePhoneNumberIdentityUserRepository)
        {
            _localizer = localizer;
            _identityUserManager = identityUserManager;
            _identityOptions = identityOptions;
            _uniquePhoneNumberIdentityUserRepository = uniquePhoneNumberIdentityUserRepository;
            
        }

        public string GrantType => PhoneNumberLoginConsts.TokenRequest.GrantTypePhonePassword;

        public async Task ValidateAsync(ExtensionGrantValidationContext context)
        {
            var raw = context.Request.Raw;
            var credential = raw.Get(PhoneNumberLoginConsts.TokenRequest.GrantType);
            if (credential == null || !credential.Equals(GrantType))
            {
                _logger.LogInformation("Invalid grant type: not allowed");
                context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, _localizer["InvalidGrant:GrantTypeInvalid"]);
                return;
            }
            var phoneNumber = raw.Get(PhoneNumberLoginConsts.TokenRequest.PhoneNumber);
            var phoneToken = raw.Get(PhoneNumberLoginConsts.TokenRequest.PhoneSmsCode);
            if (phoneNumber.IsNullOrWhiteSpace() || phoneToken.IsNullOrWhiteSpace())
            {
                _logger.LogInformation("Invalid grant type: phone number or token code not found");
                context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, _localizer["InvalidGrant:PhoneOrTokenCodeNotFound"]);
                return;
            }
            var currentUser = await _uniquePhoneNumberIdentityUserRepository.GetByConfirmedPhoneNumberAsync(phoneNumber);
            if (currentUser == null)
            {
                _logger.LogInformation("Invalid grant type: phone number not register");
                context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, _localizer["InvalidGrant:PhoneNumberNotRegister"]);
                return;
            }

            if (await _identityUserManager.IsLockedOutAsync(currentUser))
            {
                _logger.LogInformation("Authentication failed for username: {username}, reason: locked out", currentUser.UserName);
                context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, _localizer["Volo.Abp.Identity:UserLockedOut"]);
                return;
            }

            var validResult = await _identityUserManager.VerifyTwoFactorTokenAsync(currentUser, TokenOptions.DefaultPhoneProvider, phoneToken);
            if (!validResult)
            {
                _logger.LogWarning("Authentication failed for token: {0}, reason: invalid token", phoneToken);
                // 防尝试破解密码
                var identityResult = await _identityUserManager.AccessFailedAsync(currentUser);
                if (identityResult.Succeeded)
                {
                    context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, _localizer["InvalidGrant:PhoneVerifyInvalid"]);
                    //await EventService.RaiseAsync(new UserLoginFailureEvent(currentUser.UserName, $"invalid phone verify code {phoneToken}", false));
                }
                else
                {
                    _logger.LogInformation("Authentication failed for username: {username}, reason: access failed", currentUser.UserName);
                    var userAccessFailedError = identityResult.LocalizeErrors(_localizer);
                    context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, userAccessFailedError);
                    //await EventService.RaiseAsync(new UserLoginFailureEvent(currentUser.UserName, userAccessFailedError, false));
                }
                return;
            }

            var sub = await _identityUserManager.GetUserIdAsync(currentUser);

            var additionalClaims = new List<Claim>();
            if (currentUser.TenantId.HasValue)
            {
                additionalClaims.Add(new Claim(AbpClaimTypes.TenantId, currentUser.TenantId?.ToString()));
            }

            //await EventService.RaiseAsync(new UserLoginSuccessEvent(currentUser.UserName, phoneNumber, null));
            context.Result = new GrantValidationResult(sub, "sms", additionalClaims.ToArray());

            // 登录之后需要更新安全令牌
            (await _identityUserManager.UpdateSecurityStampAsync(currentUser)).CheckErrors();
        }
    }
}
